package model.tree;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Vector3d;

import raytracing.RayTracer;
import raytracing.TraceDepth;

import model.ray.Ray;

public class AdaptiveSuperSampligQuadtree implements AdaptiveSuperSampling {
	public int depth;
	public Ray rootRay;
	public TraceDepth tdepth;
	public Color leftUpperCornerColor;
	public Color leftLowerCornerColor;
	public Color rightUpperCornerColor;
	public Color rightLowerCornerColor;
	public Vector3d direction;

	public Vector3d deltaWidth;
	public Vector3d deltaHeight;
	protected AdaptiveSuperSampligQuadtree root;
	protected AdaptiveSuperSampligQuadtree rightLower;
	protected AdaptiveSuperSampligQuadtree leftLower;
	protected AdaptiveSuperSampligQuadtree rightUpper;
	
	public RayQuadTree leftUpperRay;
	public RayQuadTree rightUpperRay;
	public RayQuadTree leftLowerRay;
	public RayQuadTree rightLowerRay;
	private boolean withDivision;

	private RayTracer rayTracer;
		
	
	public AdaptiveSuperSampligQuadtree(int depth, Ray root, 
			Vector3d deltaWidth, Vector3d deltaHeight, TraceDepth tdepth, RayTracer rayTracer ) {
		this.rootRay = root;
		this.depth = depth;
		this.tdepth = tdepth;
		this.direction = root.direction;
		this.deltaWidth = deltaWidth;
		this.deltaHeight = deltaHeight;
		this.rayTracer = rayTracer;
		
		this.leftUpperRay = calculateLeftUpperRay();
		this.leftUpperCornerColor = new Color(rayTracer.getColor(leftUpperRay.ray, tdepth.getDepths()));
		
		this.rightUpperRay = caluculateRightUpperRay();
		this.rightUpperCornerColor = new Color(rayTracer.getColor(rightUpperRay.ray, tdepth.getDepths()));
		
		this.leftLowerRay = calculateLeftLowerRay();
		this.leftLowerCornerColor = new Color(rayTracer.getColor(leftLowerRay.ray, tdepth.getDepths()));
		
		this.rightLowerRay = caluculateRightLowerRay();
		this.rightLowerCornerColor = new Color(rayTracer.getColor(rightLowerRay.ray, tdepth.getDepths()));
		
		if (!leftLowerCornerColor.equals(rightLowerCornerColor) 
				|| !leftLowerCornerColor.equals(leftUpperCornerColor)
				|| !leftLowerCornerColor.equals(rightLowerCornerColor)
				|| !leftLowerCornerColor.equals(rightUpperCornerColor)) {
			subDivide();
			withDivision = true;
		} else {
			withDivision = false;
		}
	}

	@Override
	public int getDepth() {
		return depth;
	}

	@Override
	public AdaptiveSuperSampling getLeftLowerChild() {
		return root;
	}

	@Override
	public AdaptiveSuperSampling getLeftUpperChild() {
		return leftLower;
	}

	@Override
	public AdaptiveSuperSampling getRightLowerChild() {
		return rightLower;
	}

	@Override
	public AdaptiveSuperSampling getRightUpperChild() {
		return rightUpper;
	}

	@Override
	public Color pixelValue() {
		Color ret = null;
		List<Color> list = new ArrayList<Color>();
		list.add(leftUpperCornerColor);
		list.add(leftLowerCornerColor);
		list.add(rightUpperCornerColor);
		list.add(rightLowerCornerColor);
		ret = average(list);
		list.clear();
		
		list.add(ret);
		
		if (depth > 0 && withDivision) {
			list.add(root.pixelValue());
			list.add(leftLower.pixelValue());
			list.add(rightLower.pixelValue());
			list.add(rightUpper.pixelValue());
			ret = average(list);
		}
		
		return ret;
	}

	private Color average(List<Color> list) {
		int red = 0;
		int green = 0;
		int blue = 0;
		
		for (Color c: list) {
			red += c.getRed();
			green += c.getGreen();
			blue += c.getBlue();
		}
		
		int size = list.size();
		return new Color(red/size, green/size, blue/size);
	}

	@Override
	public void subDivide() {
		if (depth > 0) {
			this.root = new AdaptiveSuperSampligQuadtree(depth - 1, leftUpperRay.ray, leftUpperRay.deltaWidth, leftUpperRay.deltaHeight, tdepth, rayTracer);			
			this.rightUpper = new AdaptiveSuperSampligQuadtree(depth - 1, rightUpperRay.ray,  rightUpperRay.deltaWidth, rightUpperRay.deltaHeight, tdepth, rayTracer);
			this.leftLower = new AdaptiveSuperSampligQuadtree(depth - 1, leftLowerRay.ray, leftLowerRay.deltaWidth, leftLowerRay.deltaHeight, tdepth, rayTracer);
			this.rightLower = new AdaptiveSuperSampligQuadtree(depth - 1, rightLowerRay.ray,  rightLowerRay.deltaWidth, rightLowerRay.deltaHeight, tdepth, rayTracer);			
		}
		
	}

	private RayQuadTree caluculateRightLowerRay() {
		RayQuadTree ret = new RayQuadTree();
		Ray ray = new Ray(rootRay.origin, null);
		Vector3d newDirection = new Vector3d(this.direction);
		
		Vector3d newDeltaWidth = new Vector3d(this.deltaWidth);
		newDeltaWidth.scale(0.5);
		Vector3d newDeltaHeight = new Vector3d(this.deltaHeight);
		newDeltaHeight.scale(0.5);
		
		newDirection.add(newDeltaHeight);
		newDirection.add(newDeltaWidth);
		newDirection.normalize();
		ray.setDirection(newDirection);

		ret.ray = ray;
		ret.deltaWidth = newDeltaWidth;
		ret.deltaHeight = newDeltaHeight;
		
		return ret;
	}

	private RayQuadTree calculateLeftLowerRay() {
		RayQuadTree ret = new RayQuadTree();
		Ray ray = new Ray(rootRay.origin, null);
		Vector3d newDirection = new Vector3d(this.direction);
		
		Vector3d newDeltaWidth = new Vector3d(this.deltaWidth);
		newDeltaWidth.scale(0.5);
		Vector3d newDeltaHeight = new Vector3d(this.deltaHeight);
		newDeltaHeight.scale(0.5);
		
		newDirection.add(newDeltaHeight);
		newDirection.normalize();
		ray.setDirection(newDirection);

		ret.ray = ray;
		ret.deltaWidth = newDeltaWidth;
		ret.deltaHeight = newDeltaHeight;
		
		return ret;
	}

	private RayQuadTree calculateLeftUpperRay() {
		RayQuadTree ret = new RayQuadTree();
		Ray ray = new Ray(rootRay.origin, rootRay.direction);
		Vector3d newDeltaWidth = new Vector3d(this.deltaWidth);
		newDeltaWidth.scale(0.5);
		Vector3d newDeltaHeight = new Vector3d(this.deltaHeight);
		newDeltaHeight.scale(0.5);
		
		ret.ray = ray;
		ret.deltaWidth = newDeltaWidth;
		ret.deltaHeight = newDeltaHeight;
		
		return ret;
	}

	private RayQuadTree caluculateRightUpperRay() {
		RayQuadTree ret = new RayQuadTree();
		Ray ray = new Ray(rootRay.origin, null);
		Vector3d newDirection = new Vector3d(this.direction);
		
		Vector3d newDeltaWidth = new Vector3d(this.deltaWidth);
		newDeltaWidth.scale(0.5);
		Vector3d newDeltaHeight = new Vector3d(this.deltaHeight);
		newDeltaHeight.scale(0.5);
		
		newDirection.add(deltaWidth);
		newDirection.normalize();
		ray.setDirection(newDirection);

		ret.ray = ray;
		ret.deltaWidth = newDeltaWidth;
		ret.deltaHeight = newDeltaHeight;
		
		return ret;
	}

	/*
	 * simple structure to save requiered variables.
	 */
	private class RayQuadTree {
		Vector3d deltaWidth;
		Vector3d deltaHeight;
		Ray ray;
		
	}

}
